package com.gaojinqi.base.core.jpa.domain;

import com.gaojinqi.base.common.constant.RespConst;
import com.gaojinqi.base.common.util.FieldUtil;
import com.gaojinqi.base.core.jpa.enums.ConditionOperatorEnum;
import com.gaojinqi.base.common.exception.BizRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

import javax.persistence.JoinColumn;
import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义jpa简易通用单表/联表查询<BR>
 * 注：所有联表查询通过具体实体中外键类字段设值查询<br>
 *
 * @author gaojinqi
 * @version 1.0
 * @since 2020年04月10日
 */
public class SimpleSpecification<T> implements Specification<T> {

    private static final long serialVersionUID = 6166113882911351611L;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final SimpleJpaQuery<?> baseJpaQuery;

    public SimpleSpecification(SimpleJpaQuery<?> baseJpaQuery) {
        this.baseJpaQuery = baseJpaQuery;
    }

    /**
     * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root,
     * javax.persistence.criteria.CriteriaQuery,
     * javax.persistence.criteria.CriteriaBuilder)
     */
    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {

        List<Predicate> predicates = new ArrayList<>();

        // 传入entity时处理方式
        Object entity = baseJpaQuery.getEntity();
        if (null != entity) {
            addParameterByEntity(root, criteriaBuilder, entity, predicates);
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        }

        // 传入conditions时处理方式
        List<SimpleJpaQuery.Condition> conditions = baseJpaQuery.getConditions();
        if (null != conditions) {
            addParameterByCondition(root, criteriaBuilder, predicates, conditions);
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        }

        throw new BizRuntimeException(RespConst.RespEnum.NULL.getCode(), "请输入正确查询参数");

    }

    /**
     * 通过传入自定义查询条件执行通用查询
     */
    private void addParameterByCondition(Root<T> root, CriteriaBuilder criteriaBuilder, List<Predicate> predicates,
                                         List<SimpleJpaQuery.Condition> conditions) {

        for (int i = 0; i < conditions.size(); i++) {
            // 1.组织条件运算
            SimpleJpaQuery.Condition c = conditions.get(i);
            Predicate predicate = this.getPredicate(root, criteriaBuilder, c);

            // 2.根据逻辑运算判断
            if (ConditionOperatorEnum.OR.equals(c.conditionOperator)) {
                List<Predicate> predicatesForCondition = new ArrayList<>();
                predicatesForCondition.add(predicate);
                // 提前判断下一个参数是否仍然为or
                for (int j = i + 1; j < conditions.size(); j++) {
                    SimpleJpaQuery.Condition cForCondition = conditions.get(j);
                    // 若为OR，则将i更为当前遍历位置
                    if (ConditionOperatorEnum.OR.equals(cForCondition.conditionOperator)) {
                        i = j;
                    } else {
                        // 否则，j回退至上一遍历位置
                        i = j - 1;
                        break;
                    }
                    predicatesForCondition.add(this.getPredicate(root, criteriaBuilder, cForCondition));
                }
                // 组装or逻辑运算中所有条件
                predicate = criteriaBuilder
                        .or(predicatesForCondition.toArray(new Predicate[predicatesForCondition.size()]));
            }
            // 添加至总条件集合中
            predicates.add(predicate);
        }
    }

    /**
     * 通过传入数据库实体entity执行简单等值+AND查询
     */
    private void addParameterByEntity(Root<T> root, CriteriaBuilder criteriaBuilder, Object entity,
                                      List<Predicate> predicates) {
        // 获取查询主体字段信息
        Field[] fields = FieldUtil.getFields(entity.getClass());

        for (Field field : fields) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object value = null;
            try {
                value = field.get(entity);
            } catch (IllegalArgumentException e) {
                logger.error("get value for fieldName[" + fieldName + "],IllegalArgumentException:" + e.getMessage());
            } catch (IllegalAccessException e) {
                logger.error("get value for fieldName[" + fieldName + "],IllegalAccessException:" + e.getMessage());
            }

            // 外键前置处理。1.获取外键真实存值。2.添加外键形式的查询关系信息
            if (field.isAnnotationPresent(JoinColumn.class)) {
                Object entityJoin = value;
                // 获取查询外键字段信息
                Field[] fieldsJoin = entityJoin.getClass().getDeclaredFields();
                for (Field fieldJoin : fieldsJoin) {
                    fieldJoin.setAccessible(true);
                    String fieldNameJoin = fieldJoin.getName();
                    Object valueJoin = null;
                    try {
                        valueJoin = fieldJoin.get(entityJoin);
                    } catch (IllegalArgumentException e) {
                        logger.error("get value for fieldName[" + fieldName + "." + fieldNameJoin
                                + "],IllegalArgumentException:" + e.getMessage());
                    } catch (IllegalAccessException e) {
                        logger.error("get value for fieldName[" + fieldName + "." + fieldNameJoin
                                + "],IllegalAccessException:" + e.getMessage());
                    }
                    this.addParameter(root, criteriaBuilder, predicates, fieldName, fieldNameJoin, valueJoin);
                }
                continue;
            }

            this.addParameter(root, criteriaBuilder, predicates, fieldName, value);

        }
    }

    /**
     * 获取入参查询串
     */
    public Predicate getPredicate(Root<T> root, CriteriaBuilder criteriaBuilder, SimpleJpaQuery.Condition c) {
        Path<T> path = this.getPath(root, c.property);
        if (path != null) {
            return c.propertyOperator.getPredicate(path, criteriaBuilder, c.value1, c.value2);
        }
        return null;
    }

    /**
     * 获取入参路径
     */
    private Path<T> getPath(Root<T> root, String property) {
        // 分隔字符
        String[] propertys = StringUtils.delimitedListToStringArray(property, ".");

        // 判断合法性
        if (propertys.length == 0) {
            return null;
        }

        // 默认先取第一个
        Path<T> path = root.get(propertys[0]);

        for (int i = 1; i < propertys.length; i++) {
            path = path.get(propertys[i]);
        }
        return path;
    }

    /**
     * 组装最终查询参数
     *
     * @param root
     * @param criteriaBuilder
     * @param predicates
     * @param fieldName       字段名
     * @param fieldValue      字段值
     */
    private void addParameter(Root<T> root, CriteriaBuilder criteriaBuilder, List<Predicate> predicates,
                              String fieldName, Object fieldValue) {
        this.addParameter(root, criteriaBuilder, predicates, fieldName, null, fieldValue);
    }

    /**
     * 组装最终查询参数
     *
     * @param root
     * @param criteriaBuilder
     * @param predicates
     * @param fieldName       字段名
     * @param childFieldName  外键类字段名（选填
     * @param fieldValue      字段值
     */
    private void addParameter(Root<T> root, CriteriaBuilder criteriaBuilder, List<Predicate> predicates,
                              String fieldName, String childFieldName, Object fieldValue) {

        Path<Object> path = null;
        if (StringUtils.isEmpty(childFieldName)) {
            path = root.get(fieldName);
        } else {
            path = root.get(fieldName).get(childFieldName);
        }

        // 需过滤则读取set参数中的过滤信息
        if (!baseJpaQuery.isWhereFilter()) {
            if (fieldValue != null) {
                predicates.add(criteriaBuilder.equal(path, fieldValue));
            }
            return;
        }

        // 仅录入需检索信息
        if (baseJpaQuery.containsSelectiveKey(fieldName)) {
            // 空值搜索特殊处理
            if (fieldValue != null) {
                predicates.add(criteriaBuilder.isNull(path));
                return;
            }
            predicates.add(criteriaBuilder.equal(path, fieldValue));
            return;
        }

        // 过滤无需检索信息
        if (baseJpaQuery.containsExceptKey(fieldName)) {
            return;
        }
    }
}
